package com.xxx.nio.selector;

import com.xxx.nio.utils.ByteBufferUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

/**
 * Selector Server端
 */
@Slf4j
public class Server {

    public static void main(String[] args) throws IOException {
        // 0.创建ByteBuffer缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(16);

        // 0.创建一个selector，用来管理多个channel
        Selector selector = Selector.open();

        // 1.创建Server端的SocketChannel，并将其注册到Selector上
        ServerSocketChannel serverSc = ServerSocketChannel.open();
        serverSc.configureBlocking(false);  // 配置为非阻塞模式
        SelectionKey sscKey = serverSc.register(selector, 0, null);// 将Server端的channel注册到Selector上，返回注册结果key
        // 注册完成后，监听感兴趣的事件
        sscKey.interestOps(SelectionKey.OP_ACCEPT);    // 对于Server端，监听accept()事件
        // 总共有4种类型的事件：
        //  ①accept()-Server端接受来自Client端的连接请求
        //  ②connect()-Client端发起向Server端的连接请求
        //  ③read()：可读事件
        //  ④write()：可写事件
        log.debug("register sscKey:{}", sscKey);

        // 2.绑定Server端监听端口
        serverSc.bind(new InetSocketAddress(8080));

        while (true) {
            // 3.select()方法会一直阻塞，直到监听的事件到来
            selector.select();  // 阻塞
            // 4.当有事件到来，处理事件
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();  // selectedKeys包含了内部所有的事件
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                log.debug("key:{}", key);

                // 处理前面注册的监听事件
                // 根据不同的事件类型，分别进行处理
                if(key.isAcceptable()) {  // Server端的accept()事件
                    ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                    SocketChannel sc = ssc.accept();  // 监听的是accept()事件
                    sc.configureBlocking(false);    // 配置为非阻塞模式
                    log.debug("sc:{}", sc);
                    // 将SocketChannel也注册到Selector
                    SelectionKey scKey = sc.register(selector, 0, null);
                    // 监听感兴趣的事件
                    scKey.interestOps(SelectionKey.OP_READ);
                    log.debug("register scKey:{}", scKey);
                } else if (key.isReadable()) { // 监听的是读取数据事件
                    try {
                        SocketChannel sc = (SocketChannel) key.channel();
                        int read = sc.read(buffer);
                        if(read == -1) {  // read()返回-1表示客户端正常断开连接
                            log.debug("client close channel. sc:{}", sc);
                            key.cancel();  // cancel 会取消注册在 selector 上的 channel，并从 selectedKeys 集合中删除 key, 后续不会再监听事件
                        } else {
                            buffer.flip();  // 切换到读取模式
                            ByteBufferUtil.debugRead(buffer);  // 打印buffer
                        }
                    }catch (Exception ex) {
                        ex.printStackTrace();
                        key.cancel();  // 当Client端被强制断开连接时，需要将key的事件取消，否则会一直循环不会被select()方法阻塞
                    }
                }

//                // 也可以不处理，直接cancel（cancel方法就是将key从selectedKeys集合里移除）
//                key.cancel();

                // 最后，selectedKeys集合里存放的是发生的事件，处理完成后必须从集合里移除，否则会有异常
                iterator.remove();
            }
        }

    }
}
